---Non-blocking Lua client for zsvc.
module(..., package.seeall)

local DEBUG = false
local function trace(...)
	if DEBUG then print(...) end
end

-- Dependencies
require 'zsvc.packet'
require "socket"
local socket, string, table = socket, string, table
local assert, print, setmetatable, type, tonumber, tostring =
		assert, print, setmetatable, type, tonumber, tostring

local fmt = string.format

local Client= {}


---Connect to a Client server, returning a Client handle.
-- @param host Defaults to localhost.
-- @param port Defaults to 11211.
-- @param defer_hook An optional function, called to defer, enabling
-- non-blocking operation.
function connect(host, port, timeout)
	host, port = host or "localhost", port or 9999

	local m = setmetatable({_sn=1,_host=host,_port=port,_timeout=timeout},{__index = Client })
	m:connect()
	return m
end

function Client:chk_err(...)
	local r1,r2=...
	if not r1 then
		if self._cb_err then
			self._cb_err(self,...)
		end
		if self._s then
			self._s:close()
			self._s=nil
		end
	end
	return ...
end

---(Re-)Connect to the Client server.
function Client:connect()
	if self._s then
		self._s:close()
		self._s=nil
	end
	local conn, err = socket.connect(self._host, self._port)
	if not conn then return false, err end
	self._s = conn
	if self._timeout then
		conn:settimeout(self._timeout)
	end
	return conn
end

function Client:set_timeout(ms)
	self._timeout=ms
	if self._s then
		self._s:settimeout(ms)
	end
end

local function init_con(self,force)
	if not force then
		local sock = self._s
		if sock then return sock,true end
	end
	local sock, err = self:connect()
	if sock then return sock end
	return nil,err
end


---Send a command, asynchronously.
function Client:send(pkg)
	--trace("MSG: ", pkg:Print())
	local sock=self._s
	if not sock then return false, 'no socket' end
	return self:chk_err(sock:send(pkg.buf))
end


---Read a response (a line of text, or spec bytes), asynchronously.
function Client:receive()
	local sock=self._s
	if not sock then return false, err end
	pktrecv=zsvc.packet.new()
	local l,err = self:chk_err(sock:receive(10))
	if not l then return l,err end
	pktrecv.buf=l
	local h=pktrecv:ParseHeader()
	l,err=self:chk_err(sock:receive(h.size))
	if not l then return l,err end
	pktrecv.buf=pktrecv.buf..l
	return pktrecv
end

function Client:_call_msg(user,dal,nid,act,args)
	local np=zsvc.packet.new()
	np:SetMsg(0,{sn=self._sn,accid=user},{node=nid,act=act,args=args})
	self._sn=self._sn+1
	local res
	local ok,retry=init_con(self)
	if not ok then return ok,retry end
	if retry then
		res=self:send(np)
		if res then
			res=self:receive()
		end
		if not res then
			assert(init_con(self,true))
		end
	end
	if not res then
		assert(self:send(np))
		res=assert(self:receive())
	end
	local por,body=res:GetMsg()
	local bd=body.bd
	if bd.err then
		error(bd.err)
	elseif bd.ret then
		return true,unpack(bd.ret)
	end
	return true
end

function Client:_acall_msg(user,dal,cb,nid,act,args)
	local np=zsvc.packet.new()
	np:SetMsg(0,{sn=self._sn,accid=user},{node=nid,act=act,args=args})
	self._sn=self._sn+1
	local res,msg
	local ok,retry=init_con(self)
	if not ok then return ok,retry end
	if retry then
		res=self:send(np)
		if res then
			res,msg=self:receive()
		end
		if not res then
			assert(init_con(self,true))
		end
	end
	if not res then
		assert(self:send(np))
		res,msg=self:receive()
	end
	if not res then cb(self,res,msg);return true end
	local por,body=res:GetMsg()
	if cb then cb(self,0,body) end
	return true
end

function Client:Call(appid,nid,act,args)
	local np=zsvc.packet.new()
	np:SetMsg(appid,{sn=self._sn,accid='Admin'},{node=nid,act=act,args=args})
	self._sn=self._sn+1
	local res
	local ok,retry=init_con(self)
	if not ok then return ok,retry end
	if retry then
		res=self:send(np)
		if res then
			res=self:receive()
		end
		if not res then
			assert(init_con(self,true))
		end
	end
	if not res then
		assert(self:send(np))
		res=assert(self:receive())
	end
	local por,body=res:GetMsg()
	local bd=body.bd
	if bd.err then
		error(bd.err)
	elseif bd.ret then
		return unpack(bd.ret)
	end
end
function Client:ACall(cb,appid,nid,act,args)
	print(cb,appid,nid,act,zdc.doc.str_print(args))
	local np=zsvc.packet.new()
	np:SetMsg(appid,{sn=self._sn,accid='Admin'},{node=nid,act=act,args=args})
	self._sn=self._sn+1
	local res
	local ok,retry=init_con(self)
	if not ok then return ok,retry end
	if retry then
		res=self:send(np)
		if res then
			res=self:receive()
		end
		if not res then
			assert(init_con(self,true))
		end
	end
	if not res then
		assert(self:send(np))
		res=assert(self:receive())
	end
	local por,body=res:GetMsg()
	if cb then cb(self,0,body) end
	return 0
end
